区别 堆、栈、堆栈、队列c

堆(Heap)

数据结构中的堆

定义

堆是一种树形数据结构,满足以下性质:

用途

示例

数组表示:90, 80, 70, 60, 50)
最大堆

	  90
	/   \
   80    70
  /  \
 60  50

最小堆

    50
    /  \
  60   70
 / \
80  90

内存管理中的堆

定义

堆是程序运行时动态分配内存的区域。

特点

用途

栈(Stack)

数据结构中的栈

定义

栈是一种后进先出(LIFO)线性数据结构

特点

操作

用途

示例

栈的操作顺序

push(1) → 栈:[1]
push(2) → 栈:[1, 2]
pop()   → 返回 2,栈变为 [1]

函数调用与返回

# 栈最常见的应用是管理函数调用。当一个函数被调用时,它的局部变量、参数和返回地址会被压入栈中。当函数执行完毕后,这些数据会从栈中弹出,程序返回到调用函数的位置。
# 例如,考虑一个递归函数计算阶乘:
int factorial(int n) {
	if (n <= 1) {
		return 1;
	}
	return n * factorial(n - 1);
}
# 每次递归调用 factorial 函数时,都会在栈上创建一个新的栈帧,用于存储该次调用的参数和局部变量。当递归结束时,栈帧依次弹出,计算结果逐层返回。

表达式求值:

浏览器的历史记录:

内存管理中的栈

定义

操作系统进程线程分配的连续的物理内存区域,用于保存函数调用时的局部变量、返回地址等数据。

特点

用途

堆栈

队列

数据结构中的队列

定义

是一种先进先出(FIFO)的线性数据结构

核心特性

  1. FIFO原则:最早入队的元素最先出队。
  2. 操作端点分离
    • 入队:只能在队尾rear 插入操作(enqueue)。
    • 出队:只能在队头front 删除操作(dequeue)。
  3. 逻辑结构:元素之间是一对一的线性关系(如链表或数组的顺序存储)。

实现方式

队列的基本操作

操作 描述
入队(Enqueue) 在队尾添加新元素。
出队(Dequeue) 从队头移除元素。
判空(IsEmpty) 检查队列是否为空(front == rear)。
判满(IsFull) 检查队列是否已满(仅适用于顺序队列的固定容量场景)。
获取队头元素 返回队头元素但不删除它(如front()方法)。
image.png

内存管理中的队列

没有独立的“内存管理中的队列”区域,与堆和栈不同。内存管理中的堆和栈是操作系统或编译器管理的固定内存区域,而队列是数据结构,其内存分配依赖于具体实现(如数组在堆/栈上,链表在堆上)。

总结

数据结构

特性 堆(Heap) 栈(Stack) 队列(Queue)
数据结构 树形结构(通常为完全二叉树),支持最大堆/最小堆 线性结构,LIFO后进先出 线性结构,FIFO先进先出
操作方式 插入(push)/删除(pop)需堆化维护性质 栈顶插入(push)/删除(pop) 队尾插入(enqueue),队头删除(dequeue)
应用场景 优先队列、堆排序、图算法(如Dijkstra最短路径) 函数调用、递归、表达式求值 任务调度、广度优先搜索、缓冲区
访问速度 较慢(需动态调整树结构) 极快(仅操作栈顶指针) 通常较快(数组实现最优)
线程安全 共享时需同步机制 线程私有,无需同步 共享时需同步机制
生命周期 程序员显式操作(插入/删除) 程序员显式操作(push/pop) 程序员控制或依赖队列操作(如入队/出队逻辑)

内存管理

特性 堆(Heap) 栈(Stack) 队列(Queue)
用途 动态内存分配(如 malloc/new 分配的变量) 函数调用时存储局部变量、参数、返回地址等 无独立定义,是数据结构在内存中的实现或应用,其内存可能来自堆或栈
分配方式 手动分配和释放(程序员控制或依赖垃圾回收机制) 自动分配和释放(编译器管理,随函数调用入栈/出栈) 内存分配取决于实现(可以是堆上的动态数组/链表,或栈上的静态数组)
内存地址方向 向高地址增长(不固定,碎片化) 向低地址增长(连续且固定) 无固定方向,取决于实现(如循环队列基于数组或链表)
分配速度 较慢(需查找可用内存块,可能触发 GC) 极快(仅移动栈指针) 取决于实现(数组实现快,链表实现可能涉及堆分配)
碎片问题 外部碎片(频繁分配/释放导致不连续内存块) 无碎片(严格后进先出,内存连续释放) 内部碎片(固定大小数组实现时可能浪费空间)
线程安全 全局共享,需同步机制(如锁) 线程私有(每个线程有自己的栈),无需同步 共享时需同步机制(如多线程任务队列)
典型应用 动态对象、大块内存(如图片、数组)、跨函数生存的数据 函数调用链、局部变量、基本数据类型(如 int、float) 消息队列、I/O 缓冲区、生产者-消费者模型(如网络请求队列)
内存管理风险 内存泄漏(未释放)、悬垂指针(提前释放) 栈溢出(递归过深或局部变量过大) 缓冲区溢出(固定大小队列未检查边界)
生命周期 程序员手动分配/释放内存(或依赖GC) 系统自动管理随函数调用结束自动销毁
(局部变量生命周期与函数绑定)
程序员控制或依赖队列操作(如入队/出队逻辑)

堆、栈 在PHP中的存储内容

在 PHP 中,栈(Stack)堆(Heap) 是内存管理的两个核心区域,它们存储不同类型的数据并遵循不同的管理机制。以下是它们的存储内容及 PHP 代码示例:

1. 栈(Stack)

存储内容:

特点:

PHP 示例:

function calculate($a, $b) {
    $sum = $a + $b; // 局部变量 $sum 存储在栈中
    return $sum;
}
$result = calculate(3, 5); // $result 存储在栈中(如果未被分配到堆)

2. 堆(Heap)

存储内容:

特点:

PHP 示例:

// 示例1:对象存储在堆中
$class = new stdClass(); // 对象实例存储在堆中,$class 变量(引用)存储在栈中
$class->name = "Example";

// 示例2:数组存储在堆中
$array = [1, 2, 3]; // 数组本身存储在堆中,变量 $array 是指向堆的引用
$array['key'] = "value";

// 示例3:全局变量存储在堆中
$globalVar = "Global Data"; // 全局变量存储在堆中

关键区别总结

特性 栈(Stack) 堆(Heap)
存储内容 局部变量、函数调用上下文 对象、数组、全局变量、大字符串等
内存分配 自动管理,无需手动干预 由 Zend 引擎动态分配,垃圾回收自动回收
访问速度 快(连续内存,直接寻址) 较慢(非连续内存,需间接访问)
生命周期 函数执行结束自动释放 可能长期存在,依赖垃圾回收机制
大小限制 通常较小(由系统或 PHP 配置决定) 可动态扩展,容量更大

PHP 内存管理的特殊性

PHP 的变量实际上是 ZVAL(Zend Value) 的封装,每个变量包含值、类型、引用计数等信息。具体存储位置取决于变量的类型和大小:

1. 写时复制(Copy-On-Write):

当变量被赋值时,仅复制引用;修改时才会真正复制数据到新内存。

$a = [1, 2]; 
$b = $a;        // 引用复制,共享同一堆内存
$b[] = 3;       // 触发写时复制,$b 指向新内存

2. 垃圾回收(GC):

通过引用计数和循环垃圾回收器管理堆内存。当引用计数为 0 或循环引用无法访问时,内存被回收。

常见问题解答

Q:PHP 中的数组为什么存储在堆中?

$arr = [1, 2, 3]; // 数组的哈希表结构存储在堆中,变量 $arr 是指向堆的指针。

Q:全局变量是否也存储在堆中?

Q:字符串何时存储在堆中?

通过理解栈和堆的区别,可以更好地优化 PHP 代码,例如: